/*
 * NeuronView.java
 *
 * Created on Dec 15, 2010, 1:45:59 PM
 *
 * Copyright (c) 2010 Artur Rataj.
 *
 * This code is distributed under the terms of the GNU Library
 * General Public License, either version 2 of the license or, at
 * your option, any later version.
 */

package pl.gliwice.iitis.spatialnn.network.view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.util.*;

import java.awt.geom.*;

import pl.gliwice.iitis.graphutils.Point;
import pl.gliwice.iitis.graphutils.view.*;
import pl.gliwice.iitis.spatialnn.network.Neuron;
import pl.gliwice.iitis.spatialnn.network.NeuronType;

/**
 * A view of a neuron.
 * 
 * @author Artur Rataj
 */
public class NeuronView extends AbstractCentralView {
    /**
     * Radius of this cell.
     */
    final double RADIUS = 0.10;
    
    /**
     * A view for setting expression's color.
     */
    ExpressionView expressionView;
    /**
     * A color for the cell's body background.
     */
    static Color color;
    /**
     * A darker version of <code>color</code>.
     */
    static Color darkColor;
    /**
     * A lighter version of <code>color</code>.
     */
    static Color lightColor;
    /**
     * An outline of texts on a variable background.
     */
    static Color outlineColor;
    /**
     * Stroke for drawing bodies.
     */
    Stroke bodyStroke;
    /**
     * Stroke of dashed bars.
     */
    Stroke barStroke;
    /**
     * Negative stroke of dashed bars.
     */
    Stroke invBarStroke;
    /**
     * If a neuron view should contain a textual description.
     */
    boolean legend;

    static {
        color = new Color(20, 128, 192);
        darkColor = new Color(10, 64, 96);
        lightColor = new Color(60, 140, 230);
        outlineColor = new Color(230, 245, 255);
    }
    
    /**
     * Creates a new instance of NeuronView. 
     * 
     * @param draw mode
     * @param scale scale, default is 1.0; it effectively multiplies <code>res</code>
     * @param legend if a neuron view should contain a textual description
     * @param bw black and white mode
     */
    public NeuronView(DrawMode dm, double scale, boolean legend) {
        super(new DrawMode((int)Math.round(dm.res*scale),
                dm.bw));
        this.legend = legend;
        float[] STRIPED_BAR_DASH = new float[] {this.dm.res*0.05f, this.dm.res*0.03f};
        float STRIPED_BAR_PHASE = 0.0f;
        float[] INV_STRIPED_BAR_DASH = new float[] {this.dm.res*0.03f, this.dm.res*0.05f};
        float INV_STRIPED_BAR_PHASE = this.dm.res*0.03f;
        bodyStroke = new BasicStroke(0.08f*this.dm.res);
        barStroke = new BasicStroke(
                0.08f*this.dm.res, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f,
                STRIPED_BAR_DASH, STRIPED_BAR_PHASE);
        invBarStroke = new BasicStroke(
                0.08f*this.dm.res, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f,
                INV_STRIPED_BAR_DASH, INV_STRIPED_BAR_PHASE);
        expressionView = new ExpressionView(dm);
    }
    /**
     * Draws the expressions of the cell.
     * 
     * @param expressions expressions to draw
     * @param at cell's center
     * @param g graphics object
     */
    protected void draw(double[] expressions, Point at, Graphics2D g) {
        int num = expressions.length;
        final double AZIMUTH = 135.0;
        final double ANGLE = -90.0;
        double extent = ANGLE/num;
        for(int i = 0; i < num; ++i) {
            double value = expressions[i];
            expressionView.draw(expressionView.getExpressionValue(value), at, g);
            double r = RADIUS*(1.1 + value)*dm.res;
            Rectangle2D.Double ellipse = new Rectangle2D.Double(
                    at.x - r, at.y - r, r*2, r*2);
            double angle = AZIMUTH + i*ANGLE/num;
            Color c = g.getColor();
            g.fill(new Arc2D.Double(ellipse, angle, extent, Arc2D.Double.PIE));
            g.setColor(new Color(c.getRed()/2, c.getGreen()/2, c.getBlue()/2));
            if(extent >= 360 - 1e-3)
                g.draw(new Ellipse2D.Double(ellipse.x, ellipse.y, ellipse.width, ellipse.height));
            else
                g.draw(new Arc2D.Double(ellipse, angle, extent, Arc2D.Double.PIE));
            /*
            Color b = new Color(
                    (wave((c.getRed() + 15)*5)),
                    (wave((c.getGreen() + 10)*10)),
                    (wave((c.getBlue() + 5)*15)));
            final int SECTIONS = 1;
            double se = extent/(SECTIONS*2);
            int count = 0;
            for(double a = angle + se/2 + se/4; a < angle + extent - 1e-6;
                    a += extent/SECTIONS) {
                double re = RADIUS*(1.1 + Math.min(2.0, 10*value))*res;
                ellipse = new Rectangle2D.Double(
                        at.x - re, at.y - re, re*2, re*2);
                g.setColor(b);
                g.fill(new Arc2D.Double(ellipse, a, se/2, Arc2D.Double.PIE));
                g.setColor(new Color(b.getRed()/2, b.getGreen()/2, b.getBlue()/2));
                g.draw(new Arc2D.Double(ellipse, a, se/2, Arc2D.Double.PIE));
                ++count;
            }
             */
        }
    }
    /**
     * Draws a "striped bar" to the left or to the right.
     * The foreground color must be defined a priori.
     * 
     * @param value value to visualize, 0 ... 1
     * @param left true for left, false for right
     * @param at cell's center
     * @param g graphics object
     */
    protected void draw(double value, boolean left, Point at, Graphics2D g) {
        final double MAX_LENGTH  = 0.21;
        Stroke prevStroke = g.getStroke();
        double dir = left ? -1.0 : 1.0;
        double r1 = RADIUS*(1.25 + 0.25*dir)*dm.res;
        double r2 = MAX_LENGTH*0.5*value*dm.res;
        g.setStroke(barStroke);
        g.draw(new Line2D.Double(at.x - r1, at.y + r2, at.x - r1, at.y  - r2));
        g.setStroke(invBarStroke);
        g.setColor(Color.ORANGE);
        g.draw(new Line2D.Double(at.x - r1, at.y + r2, at.x - r1, at.y  - r2));
        g.setStroke(prevStroke);
    }
    /**
     * Draws a neuron's miscelanous description, below the circle.
     * 
     * @param neuron neuron, whose layerNum to draw
     * @param at the neuron's view center
     * @param radius radius of the neuron's circle
     * @param g graphics object
     */
    protected void drawMisc(Neuron neuron, Point at, double radius, Graphics2D g) {
        int outline = (int)Math.round(radius/8);
        String t = "";// + neuron.key;// + ":" + neuron.layerNum;
        Rectangle2D bounds = AbstractImageView.getStringBounds(g, t);
        int ascent = g.getFontMetrics().getAscent();
        double xPos = at.x - bounds.getWidth()/2.0;
        double yPos = at.y + ascent + radius;
        g.setColor(outlineColor);
        for(int b = -outline; b <= outline; ++b)
            for(int a = -outline; a <= outline; ++a) {
                if(a !=0 && b != 0)
                    g.drawString(t,
                        (float)(xPos + a),
                        (float)(yPos + b));
            }
        g.setColor(Color.BLACK);
        g.drawString(t,
            (float)(xPos), (float)(yPos));
    }
    @Override
    public void draw(Drawable object, Point at, Graphics2D g) {
        Neuron neuron = (Neuron)object;
        double r = RADIUS*dm.res;
        /*
        draw(neuron.expression, at, g);
        */
        Stroke prevStroke = g.getStroke();
        if(dm.bw) {
            List<Shape> sh = new LinkedList<>();
            boolean fill = false;
            double r2 = r*0.9;
            switch((neuron.layerNum + 6)%6) {
                case 0:
                    sh.add(new Ellipse2D.Double(at.x - r, at.y - r, r*2, r*2));
                    break;
                    
                case 1:
                    sh.add(new Rectangle2D.Double(at.x - r2, at.y - r2, r2*2, r2*2));
                    fill = true;
                    break;
                    
                case 2:
                    sh.add(new Line2D.Double(at.x - r, at.y, at.x + r, at.y));
                    sh.add(new Line2D.Double(at.x, at.y + r, at.x, at.y - r));
                    break;
                    
                case 3:
                    sh.add(new Ellipse2D.Double(at.x - r2, at.y - r2, r2*2, r2*2));
                    fill = true;
                    break;
                    
                case 4:
                    sh.add(new Line2D.Double(at.x - r, at.y + r, at.x + r, at.y + r));
                    sh.add(new Line2D.Double(at.x - r, at.y + r, at.x, at.y - r));
                    sh.add(new Line2D.Double(at.x + r, at.y + r, at.x, at.y - r));
                    break;
                    
                case 5:
                    sh.add(new Rectangle2D.Double(at.x - r, at.y - r, r*2, r*2));
                    break;
                    
                default:
                    throw new RuntimeException("no shape");
            }
            g.setStroke(bodyStroke);
            for(Shape s : sh)
                if(fill)
                    g.fill(s);
                else
                    g.draw(s);
            g.setStroke(prevStroke);
        } else {
            g.setColor(NetworkView.getLayerColor(neuron.layerNum));
            if(neuron.type.getConstructionParams(false).inAngleSigma < 0 && neuron.inputs.length != 2)
                ;//g.setColor(Color.RED);
            Ellipse2D.Double ellipse = new Ellipse2D.Double(at.x - r, at.y - r, r*2, r*2);
            g.fill(ellipse);
            g.setColor(darkColor);
            g.draw(ellipse);
        }
        if(legend) {
            String t = ("" + neuron.type.getSymbolChar()).trim();
            t = neuron.layerNum + t;
            Rectangle2D bounds = AbstractImageView.getStringBounds(g, t);
            int ascent = g.getFontMetrics().getAscent();
            g.setColor(Color.WHITE);
            g.drawString(t,
                    (float)(at.x - bounds.getWidth()/2.0),
                    (float)(at.y + ascent - bounds.getHeight()/2.0 - 1));
        }
        drawMisc(neuron, at, r, g);
        // draw(neuron.layerNum, false, at, g);
        //g.setColor(Color.RED);
        // draw(neuron.getAge(), true, at, g);
    }
}
